package edu.unicen.compilers.lexicon;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.FileInputStream;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.HashMap;

import edu.unicen.compilers.actions.SemanticAction;
import edu.unicen.compilers.actions.SemanticAction1;
import edu.unicen.compilers.actions.SemanticAction11;
import edu.unicen.compilers.actions.SemanticAction12;
import edu.unicen.compilers.actions.SemanticAction13;
import edu.unicen.compilers.actions.SemanticAction14;
import edu.unicen.compilers.actions.SemanticAction2;
import edu.unicen.compilers.actions.SemanticAction3;
import edu.unicen.compilers.actions.SemanticAction4;
import edu.unicen.compilers.actions.SemanticAction5;
import edu.unicen.compilers.actions.SemanticAction6;
import edu.unicen.compilers.actions.SemanticAction7;
import edu.unicen.compilers.actions.SemanticAction8;
import edu.unicen.compilers.actions.SemanticAction9;
import edu.unicen.compilers.parser.Parser;
import edu.unicen.compilers.parser.ParserVal;

public final class LexicalAnalyzer {
	//File properties
	private int lineNumber;
	private boolean debugg;
	private BufferedReader reader;
	//Token
	private int token;
	private String lexeme;
	private char lastCharacter;
	//Symbols
	HashMap<String,ArrayList<String>> hashTable;
	//Matrix
	private int[][] transactionMatrix;
	private SemanticAction[][] semanticActionsMatrix;
	private String val;
	private Parser parser;
	private String lastConstant=new String();
	
	public String getLastConstant() {
		return lastConstant;
	}

	public void setLastConstant(String lastConstant) {
		this.lastConstant = lastConstant;
	}

	public void setToken(int tok){
		this.token=tok;
	}
	
	public void setDebuggMode(boolean d){
		this.debugg= d;
	}
	
	public LexicalAnalyzer(String filePath, Parser par){
		//Init Lexical Analyzer
		this.parser = par; 
		this.initMatrix();
		this.generateTransactionMatrix();
		this.genereateSemanticActionsMatrix();
		this.lexeme="";
		this.lineNumber=1;
		this.lastCharacter='\0';
		this.hashTable=new HashMap<String,ArrayList<String>>();
		this.storeReserverdWords();
		//Open File
		try {
			this.reader= new BufferedReader(new InputStreamReader(new FileInputStream(filePath)));
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}
	}
	
		private void storeReserverdWords(){
			
			ArrayList<String> v=new ArrayList<String>();
			v.add("Palabra Reservada");
			hashTable.put("if",v); 
		
			ArrayList<String> v1=new ArrayList<String>();
			v1.add("Palabra Reservada");
			hashTable.put("then",v1); 
			
			ArrayList<String> v2=new ArrayList<String>();
			v2.add("Palabra Reservada");
			hashTable.put("else",v2); 
			
			ArrayList<String> v3=new ArrayList<String>();
			v3.add("Palabra Reservada");
			hashTable.put("begin",v3); 
			
			ArrayList<String> v4=new ArrayList<String>();
			v4.add("Palabra Reservada");
			hashTable.put("end",v4); 
			
			ArrayList<String> v5=new ArrayList<String>();
			v5.add("Palabra Reservada");
			hashTable.put("long",v5); 
			
			ArrayList<String> v6=new ArrayList<String>();
			v6.add("Palabra Reservada");
			hashTable.put("function",v6); 
			
			ArrayList<String> v7=new ArrayList<String>();
			v7.add("Palabra Reservada");
			hashTable.put("for",v7); 
			
			ArrayList<String> v8=new ArrayList<String>();
			v8.add("Palabra Reservada");
			hashTable.put("return",v8); 		
	}
		
	
	public void putSymbol(String key,ArrayList<String> params){
		if(!hashTable.containsKey(key)){
			hashTable.put(key,params);
		}
	}
	
	public ArrayList<String> getParamsFromSymbol(String key){
		return hashTable.get(key);
	}
	public void setParamsFromSymbol(String key,ArrayList<String> params){
		hashTable.remove(key);
		hashTable.put(key, params);
	}
	
	public char getLastCharacter() {
		return lastCharacter;
	}

	public void setLastCharacter(char lastCharacter) {
		this.lastCharacter = lastCharacter;
	}
	
	public void incrementLineNumber(){
		this.lineNumber+=1;
	}
	
	public int getLineNumber(){
		return this.lineNumber;
	}
	
	public void setLexeme(String lex) {
		this.lexeme = lex;
	}
	
	public String getPartialLexeme() {
		return this.lexeme;
	}

	private void initMatrix(){
		this.transactionMatrix= new int[13][21];
		this.semanticActionsMatrix=new SemanticAction[13][21];
		for (int i = 0; i < 13; i++) {
			for (int j = 0; j <= 19; j++) {
				this.transactionMatrix[i][j]=11;
				this.semanticActionsMatrix[i][j]=null;
			}
		}
	}
	
	private void generateTransactionMatrix(){
		this.transactionMatrix[0][0]=1;
		this.transactionMatrix[0][1]=2;
		this.transactionMatrix[0][10]=3;
		this.transactionMatrix[0][11]=4;
		this.transactionMatrix[0][12]=5;
		this.transactionMatrix[0][13]=6;
		this.transactionMatrix[0][14]=11;
		this.transactionMatrix[0][15]=0;
		this.transactionMatrix[0][16]=0;
		this.transactionMatrix[0][17]=0;
		this.transactionMatrix[0][18]=9;
		
		this.transactionMatrix[0][19]=7;
		
		for (int i=0;i<13;i++)
			this.transactionMatrix[i][20]=i;
		
		this.transactionMatrix[1][0]=1;
		this.transactionMatrix[1][1]=1;
		
		this.transactionMatrix[2][1]=2;
		
		this.transactionMatrix[7][19]=8;
		
		this.transactionMatrix[8][0]=8;
		this.transactionMatrix[8][1]=8;
		this.transactionMatrix[8][2]=8;
		this.transactionMatrix[8][3]=8;
		this.transactionMatrix[8][4]=8;
		this.transactionMatrix[8][5]=8;
		this.transactionMatrix[8][6]=8;
		this.transactionMatrix[8][7]=8;
		this.transactionMatrix[8][8]=8;
		this.transactionMatrix[8][9]=8;
		this.transactionMatrix[8][10]=8;
		this.transactionMatrix[8][11]=8;
		this.transactionMatrix[8][12]=8;
		this.transactionMatrix[8][13]=8;
		this.transactionMatrix[8][14]=8;
		this.transactionMatrix[8][15]=0;
		this.transactionMatrix[8][16]=8;
		this.transactionMatrix[8][17]=8;
		this.transactionMatrix[8][18]=8;
		this.transactionMatrix[8][19]=8;
		this.transactionMatrix[8][20]=8;
		
		
		for (int i = 0; i <= 19; i++) {
			this.transactionMatrix[9][i]=9;
			this.transactionMatrix[10][i]=9;
		}
		this.transactionMatrix[9][2]=10;
		this.transactionMatrix[9][18]=11;
		this.transactionMatrix[9][15]=11;
		this.transactionMatrix[10][16]=10;
		
		
				
	}
	
	private void genereateSemanticActionsMatrix(){
		for (int i=0;i<13;i++)
			this.semanticActionsMatrix[i][20]=new SemanticAction14(this,parser);
		
		this.semanticActionsMatrix[0][0]=new SemanticAction1(this,parser);
		this.semanticActionsMatrix[0][1]=new SemanticAction1(this,parser);
		this.semanticActionsMatrix[0][2]=new SemanticAction7(this,parser);
		this.semanticActionsMatrix[0][3]=new SemanticAction7(this,parser);
		this.semanticActionsMatrix[0][4]=new SemanticAction7(this,parser);
		this.semanticActionsMatrix[0][5]=new SemanticAction7(this,parser);
		this.semanticActionsMatrix[0][6]=new SemanticAction7(this,parser);
		this.semanticActionsMatrix[0][7]=new SemanticAction7(this,parser);
		this.semanticActionsMatrix[0][8]=new SemanticAction7(this,parser);
		this.semanticActionsMatrix[0][9]=new SemanticAction7(this,parser);
		this.semanticActionsMatrix[0][10]=new SemanticAction1(this,parser);
		this.semanticActionsMatrix[0][11]=new SemanticAction1(this,parser);
		this.semanticActionsMatrix[0][12]=new SemanticAction1(this,parser);
		this.semanticActionsMatrix[0][13]=new SemanticAction1(this,parser);
		this.semanticActionsMatrix[0][14]=new SemanticAction7(this,parser);//mal
		this.semanticActionsMatrix[0][15]=new SemanticAction5(this,parser);
		this.semanticActionsMatrix[0][16]=new SemanticAction6(this,parser);
		this.semanticActionsMatrix[0][17]=new SemanticAction6(this,parser);
		this.semanticActionsMatrix[0][18]=new SemanticAction6(this,parser);
		this.semanticActionsMatrix[0][19]=new SemanticAction6(this,parser);
		
		for (int i = 0; i <= 19; i++) {
			this.semanticActionsMatrix[1][i]=new SemanticAction3(this,parser);
			this.semanticActionsMatrix[2][i]=new SemanticAction4(this,parser);
			this.semanticActionsMatrix[3][i]=new SemanticAction9(this,parser);
			this.semanticActionsMatrix[4][i]=new SemanticAction9(this,parser);
			this.semanticActionsMatrix[5][i]=new SemanticAction9(this,parser);
			this.semanticActionsMatrix[7][i]=new SemanticAction9(this,parser);
			this.semanticActionsMatrix[6][i]=new SemanticAction14(this,parser);
			this.semanticActionsMatrix[9][i]=new SemanticAction2(this,parser);
			this.semanticActionsMatrix[10][i]=new SemanticAction2(this,parser);
		}
		this.semanticActionsMatrix[1][0]=new SemanticAction2(this,parser);
		this.semanticActionsMatrix[1][1]=new SemanticAction2(this,parser);
		this.semanticActionsMatrix[2][1]=new SemanticAction2(this,parser);
		this.semanticActionsMatrix[3][10]=new SemanticAction8(this,parser);
		this.semanticActionsMatrix[4][10]=new SemanticAction8(this,parser);
		this.semanticActionsMatrix[5][10]=new SemanticAction8(this,parser);
		this.semanticActionsMatrix[6][10]=new SemanticAction8(this,parser);
		this.semanticActionsMatrix[7][19]=new SemanticAction12(this,parser);
		this.semanticActionsMatrix[8][15]=new SemanticAction5(this,parser);
		this.semanticActionsMatrix[8][20]=null;
		this.semanticActionsMatrix[9][2]=null;
		this.semanticActionsMatrix[9][18]=new SemanticAction11(this,parser);
		this.semanticActionsMatrix[9][15]=new SemanticAction13(this,parser);
		this.semanticActionsMatrix[10][15]=new SemanticAction5(this,parser);
		this.semanticActionsMatrix[10][16]=null;
		
		this.semanticActionsMatrix[9][20]=new SemanticAction2(this,parser);
		this.semanticActionsMatrix[10][20]=new SemanticAction2(this,parser);
	}
	
	private int inputMapToMatrixIndex(int readChar){
		//Digits
		if (readChar>=48 && readChar <=57)
			return 1;
		//Characters
		if ( (readChar>=65 && readChar<=90 ) | (readChar>=97 && readChar<=122 ))
			return 0;
		//Signs
		if (readChar==43) // +
			return 2;
		if (readChar==45) // -
			return 3;
		if (readChar==42) // *
			return 4;
		if (readChar==40) // (
			return 5;
		if (readChar==41) // )
			return 6;
		if (readChar==44) // ,
			return 7;
		if (readChar==59) // ;
			return 8;
		if (readChar==58) // :
			return 9;
		if (readChar==61) // =
			return 10;
		if (readChar==62) // >
			return 11;
		if (readChar==60) // <
			return 12;
		if (readChar==33) // !
			return 13;
		if (readChar==47) // /
			return 14;
		if (readChar==34) // " 
			return 18;
		if (readChar==92) // \ 
			return 19;
		//Special Charset
		if (readChar==32) //space
			return 17;
		if ((readChar==9) || (readChar==13)) //tab and carriage return
			return 16;
		if (readChar==10) //new line
			return 15;
		return -1;
	}
	
	public int getToken(){
		int state=0;
		int character;
		this.lexeme="";
		token=-1;
		boolean endOfFile=false;
		boolean firstRun=true;
		while (state < 11 && !endOfFile){
			try {
				if (debugg)
					System.out.print("Estado actual "+ state);
				if ((int)lastCharacter != 0 && firstRun){
					character= (int)this.lastCharacter;
					firstRun = false;
				}else{
					character = this.reader.read();
					firstRun = false;
				}
				if (character== -1 ){
					endOfFile=true;
					firstRun = false;
					this.lastCharacter=0;
				}else{
					this.setLastCharacter((char)character);
					int col= this.inputMapToMatrixIndex(character);
					if (debugg)
						System.out.print(" caracter "+  col + "["+(char)character+"]");
					if (col==-1)
						col=20;
					if (this.semanticActionsMatrix[state][col]!= null)
						this.semanticActionsMatrix[state][col].execute();
					state=this.transactionMatrix[state][col];
					if (debugg)
						System.out.print(" estado nuevo "+  state + " \n");
					if (state==11 && lexeme.length()==0)
						state=0;
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return this.token;
	}

	public ParserVal getValue() {
		return new ParserVal(val);
	}

	public void setLastVal(String format) {
		this.val=format;
	}

	public void removeSymbol(String format) {
		this.hashTable.remove(format);
	}

	public void printSymbolTable() {
		for (String element : this.hashTable.keySet()) {
			System.out.println("Clave: " + element +" - "+ this.hashTable.get(element) );
		}
	}
	
	public void updateSignedLong(String aLong){
		ArrayList<String> params=hashTable.get(aLong);
		DecimalFormat df = new DecimalFormat("#########");
		Double number= Double.valueOf(aLong);
		if (params.contains("USED") ){
			if (! this.hashTable.containsKey(df.format(number*-1))){ 
				ArrayList<String> v=new ArrayList<String>();
				v.add("CONST");
				v.add("USED");
				this.putSymbol(df.format(number*-1),v);
			}
		}else{
			this.removeSymbol(aLong);
			//Put a new signed entry in symbols table
			ArrayList<String> v=new ArrayList<String>();
			v.add("CONST");
			v.add("USED");
			this.putSymbol(df.format(number*-1),v);
		}
		return ;
	}
	
	public void updateLong(String aLong){
		ArrayList<String> params=hashTable.get(aLong);
		if (! params.contains("USED"))
			params.add("USED");
		return ;
	}
	
	public String getLexeme(){
		return this.lexeme;
	}
	
	public static void main(String[] args){
		LexicalAnalyzer la=new LexicalAnalyzer("./re-entrega1/test/reconocimiento-cadenas.txt", new Parser());
		la.setDebuggMode(false);
		int tok=la.getToken();
		while (tok!=-1){
			System.out.println("Token " + tok  + " " + la.getLexeme() );
			tok=la.getToken();
		}

	}
}
